#include "KUtility.h"
#include <Psapi.h>

K_ANONYMOUS_NAMESPACE_BEG

template <class T>
inline size_t bits(T n)
{
    size_t c = 0;
    for (; n != 0; ++c) {
        n &= n - 1;
    }
    return c;
}

inline LONGLONG utc(const FILETIME &ft)
{
    LARGE_INTEGER li;
    li.LowPart = ft.dwLowDateTime;
    li.HighPart = ft.dwHighDateTime;
    return li.QuadPart;
}

bool getCpuRate(KUtility::cpu_rate_cache_t &cache, int *rate, int max)
{
    assert(rate != NULL && max > 0);
    LONGLONG cpuTime = (utc(cache.k) + utc(cache.u)) / cache.n;
    LONGLONG sysTime = utc(cache.s);
    if (cache.cpuLast <= 0 || cache.sysLast <= 0) {
        cache.cpuLast = cpuTime;
        cache.sysLast = sysTime;
    } else if (cpuTime >= cache.cpuLast && sysTime > cache.sysLast) {
        if ((*rate = int((cpuTime - cache.cpuLast) * max / (sysTime - cache.sysLast))) > max) {
            *rate = max;
        }
        cache.cpuLast = cpuTime;
        cache.sysLast = sysTime;
        return true;
    }
    return false;
}

K_ANONYMOUS_NAMESPACE_END

void KUtility::cpu_rate_cache_t::update(HANDLE hProcess)
{
    size_t num = numOfAvailableProcessors(hProcess);
    assert(num > 0);
    if (n != num) {
        cpuLast = sysLast = 0;
        n = num;
    }
}

bool KUtility::getProcessCpuRate(cpu_rate_cache_t &cache, int *rate, HANDLE hProcess, int max)
{
    if (GetProcessTimes(hProcess ? hProcess : GetCurrentProcess(), &cache.c, &cache.e, &cache.k, &cache.u)) {
        GetSystemTimeAsFileTime(&cache.s);
        cache.update(hProcess);
        return getCpuRate(cache, rate, max);
    }
    return false;
}

bool KUtility::getProcessMemUsage(uint64_t *usage, HANDLE hProcess)
{
    assert(usage != NULL);
    PROCESS_MEMORY_COUNTERS pmc;
    if (GetProcessMemoryInfo(hProcess ? hProcess : GetCurrentProcess(), &pmc, sizeof pmc)) {
        *usage = pmc.WorkingSetSize;
        return true;
    }
    return false;
}

bool KUtility::getThreadCpuRate(cpu_rate_cache_t &cache, int *rate, HANDLE hThread, int max)
{
    if (GetThreadTimes(hThread ? hThread : GetCurrentThread(), &cache.c, &cache.e, &cache.k, &cache.u)) {
        GetSystemTimeAsFileTime(&cache.s);
        // For current process only
        cache.update();
        return getCpuRate(cache, rate, max);
    }
    return false;
}

size_t KUtility::numOfAvailableProcessors(HANDLE hProcess)
{
    DWORD_PTR p, s;
    if (GetProcessAffinityMask(hProcess ? hProcess : GetCurrentProcess(), &p, &s)) {
        return bits(p);
    }
    return numOfProcessors();
}

size_t KUtility::numOfProcessors()
{
    SYSTEM_INFO si;
    GetSystemInfo(&si);
    return size_t(si.dwNumberOfProcessors);
}

size_t KUtility::pageSize()
{
    SYSTEM_INFO si;
    GetSystemInfo(&si);
    return size_t(si.dwPageSize);
}